package featuremodel;

import java.util.LinkedList;
import java.util.Vector;

import featuremodel.constraint.AlternativeConstraint;
import featuremodel.constraint.ExcludeCompositeConstraint;
import featuremodel.constraint.ExcludeConstraint;
import featuremodel.constraint.MRequireCompositeConstraint;
import featuremodel.constraint.MRequireConstraint;
import featuremodel.constraint.MandatoryConstraint;
import featuremodel.constraint.OptionalConstraint;
import featuremodel.constraint.OrConstraint;
import featuremodel.constraint.RequireCompositeConstraint;
import featuremodel.constraint.RequireConstraint;
import featuremodel.constraint.SetBindConstraint;
import featuremodel.constraint.SetUnbindConstraint;
import featuremodel.constraint.SetUndecidedConstraint;

import skyblue.Constraint;
import skyblue.SkyBlue;
import skyblue.SkyBlueDef;
import skyblue.Strength;
import skyblue.Value;
import skyblue.Variable;

public class FeatureModel {

	private SkyBlue system;
	private Vector<Variable> features;
	private Vector<FeatureGroup> featureGroups;
	private Vector<Constraint> constraints;

	public FeatureModel() {

		this.system = new SkyBlue(SkyBlueDef.WEAKEST_STRENGTH);
		this.constraints = new Vector<Constraint>();
		this.features = new Vector<Variable>();
		this.featureGroups = new Vector<FeatureGroup>();
	}

	// ***The User Action***
	public Variable createFeature(String name) {
		Variable feature = new Variable(addFeatureValue(name));
		this.features.add(feature);
		addSetUndecidedConstraint(feature, SkyBlueDef.WEAKEST_STRENGTH);
		return feature;
	}

	private Value addFeatureValue(String name) {

		return new Value(new FeatureRep(name));

	}

	public void removeFeatureBasic(Variable feature) {
		LinkedList<Constraint> constraints = feature.getConstraints();
		for (int i = 0; i < constraints.size(); i++) {
			removeConstraint(constraints.get(i));
		}
		// Q
		features.remove(feature);

	}

	public void removeFeature(Variable feature) {
		LinkedList<Constraint> constraints = feature.getConstraints();
		for (int i = 0; i < constraints.size(); i++) {
			removeConstraint(constraints.get(i));
		}
		FeatureRep fp = (FeatureRep) feature.getValue().getValuerep();
		Vector<FeatureGroup> fgs = fp.getFeatureGroups();
		for (int j = 0; j < fgs.size(); j++) {
			removeFeatureGroup(fgs.get(j));
		}
		features.remove(feature);

	}

	// TODO Change the strength of the constraint dymaniclly
	public void changeConstraintStrength(Constraint cn, Strength strength) {

	}

	public void setMandatory(Variable child, Variable parent, Strength strength) {
		FeatureRep fp = (FeatureRep) child.getValue().getValuerep();
		if (fp.isUndefined()) {
			fp.setParent(parent);
			fp.setMandatory(true);
			fp.setUndefined(false);

			addMandatoryConstraint(child, parent, strength);
		} else {
			System.out.println("Feature " + fp.getName()
					+ " already has a parent!");
		}

	}

	public void removeMandatory(Variable f) {
		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();
		if ((!fp.isUndefined()) && fp.isMandatory()) {
			for (int i = 0; i < f.getConstraints().size(); i++) {
				Constraint temp = f.getConstraints().get(i);

				if (temp.getConstraintType().equals(
						FeatureModelDef.MANDOTARY_CONSTRAINT)) {
					if (((MandatoryConstraint) temp).getChild().equals(f)) {
						removeConstraint(temp);
					}

				}
			}
			fp.setParent(null);
			fp.setMandatory(false);
			fp.setUndefined(true);
		} else {
			System.out.println("The feature is not mandatory!");
		}
	}

	public void setOptional(Variable child, Variable parent, Strength strength) {
		FeatureRep fp = (FeatureRep) child.getValue().getValuerep();
		if (fp.isUndefined()) {
			fp.setParent(parent);
			fp.setMandatory(false);
			fp.setUndefined(false);

			addOptionalConstraint(child, parent, strength);

		} else {
			System.out.println("Feature " + fp.getName()
					+ "already has a parent!");
		}

	}

	public void removeOptional(Variable f) {
		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();
		if (!fp.isUndefined() && !fp.isMandatory()) {
			for (int i = 0; i < f.getConstraints().size(); i++) {
				Constraint temp = f.getConstraints().get(i);
				if (temp.getConstraintType().equals(
						FeatureModelDef.OPTIONAL_CONSTRAINT)) {
					if (((OptionalConstraint) temp).getChild().equals(f)) {
						removeConstraint(temp);
					}

				}
			}
			fp.setParent(null);
			fp.setMandatory(false);
			fp.setUndefined(true);
		} else {
			System.out.println("The feature is not optional! ");
		}

	}

	public void setRoot(Variable f) {

		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();
		fp.setRoot(true);
		// The Require can only be used here.
		addSetBindConstraint(f, SkyBlueDef.REQUIRE_STRENGTH);
	}

	public void removeConstraint(Constraint cn) {

		getSystem().removeConstraint(cn);

	}

	// ***START OF BASIC FEATURE MODEL***
	public void setOr(Variable parent, Vector<Variable> children,
			Strength strength) {

		addOrConstraint(parent, children, strength);

	}

	// f can be any variable of the or constraint
	public void removeOr(Variable f) {
		for (int i = 0; i < f.getConstraints().size(); i++) {
			Constraint temp = f.getConstraints().get(i);
			if (temp.getConstraintType().equals(FeatureModelDef.OR_CONSTRAINT)) {
				removeConstraint(temp);

			}
		}

	}

	public void setAlternative(Variable parent, Vector<Variable> children,
			Strength strength) {
		addAlternativeConstraint(parent, children, strength);
	}

	// f can be any variable of the or constraint
	public void removeAlternative(Variable f) {
		for (int i = 0; i < f.getConstraints().size(); i++) {
			Constraint temp = f.getConstraints().get(i);
			if (temp.getConstraintType().equals(
					FeatureModelDef.ALTERNATIVE_CONSTRAINT)) {
				removeConstraint(temp);
			}
		}
	}

	private FeatureGroup createFeatureGroup(Vector<Variable> variables,
			String predicateType) {

		FeatureGroup fg = new FeatureGroup(variables, predicateType);
		featureGroups.add(fg);
		return fg;
	}

	public void removeFeatureGroup(FeatureGroup featureGroup) {
		LinkedList<Constraint> cns = featureGroup.getConstraints();
		for (int i = 0; i < cns.size(); i++) {
			removeConstraint(cns.get(i));
		}
		featureGroups.remove(featureGroup);

	}

	// add the decomposition refinement (f and its parent)
	public void setDRelation(Variable f) {

		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();

		if (!fp.isUndefined()) {
			fp.setRefinementType(FeatureModelDef.DECOMPOSITITON);
		}

	}

	// add the characterized refinement (f and its parent)
	public void setCRelation(Variable f) {
		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();

		if (!fp.isUndefined()) {
			fp.setRefinementType(FeatureModelDef.CHARICTERIZATION);
		}

	}

	// add the specialization refinement (f and its parent)
	public void setSRelationship(Variable f) {
		FeatureRep fp = (FeatureRep) f.getValue().getValuerep();

		if (!fp.isUndefined()) {
			fp.setRefinementType(FeatureModelDef.SPECILIZATION);
		}
	}

	// ***START OF ADD THE CONSTRAINTS***
	public void addMandatoryConstraint(Variable feature, Variable parent,
			Strength strength) {
		MandatoryConstraint mcn = new MandatoryConstraint(feature, strength,
				parent);
		getConstraints().add(mcn);
		getSystem().addConstraint(mcn);

	}

	public void addOptionalConstraint(Variable child, Variable parent,
			Strength strength) {
		OptionalConstraint ocn = new OptionalConstraint(child, parent, strength);
		getConstraints().add(ocn);
		getSystem().addConstraint(ocn);

	}

	public void addRequireConstraint(Variable parentFeature,
			Variable childFeature, Strength strength) {
		RequireConstraint cn = new RequireConstraint(parentFeature,
				childFeature, strength);
		getConstraints().add(cn);
		getSystem().addConstraint(cn);

	}

	public void addExcludeConstraint(Variable a, Variable b, Strength strength) {
		ExcludeConstraint ecn = new ExcludeConstraint(a, b, strength);
		getConstraints().add(ecn);
		getSystem().addConstraint(ecn);

	}

	public void addAlternativeConstraint(Variable a, Vector<Variable> children,
			Strength strength) {
		AlternativeConstraint acn = new AlternativeConstraint(a, children,
				strength);
		getConstraints().add(acn);
		getSystem().addConstraint(acn);
	}

	public void addOrConstraint(Variable a, Vector<Variable> children,
			Strength strength) {
		OrConstraint orcn = new OrConstraint(a, children, strength);
		getConstraints().add(orcn);
		getSystem().addConstraint(orcn);
	}

	public void addMRequireConstraint(Variable a, Variable b, Strength strength) {
		MRequireConstraint mrcn = new MRequireConstraint(a, b, strength);
		getConstraints().add(mrcn);
		getSystem().addConstraint(mrcn);
	}

	public void addMRequireCompositeConstraint(FeatureGroup featureGroup1,
			String g1Type, FeatureGroup featureGroup2, String g2Type,
			Strength strength) {

		MRequireCompositeConstraint mrccn = new MRequireCompositeConstraint(
				featureGroup1, g1Type, featureGroup2, g2Type, strength);
		getConstraints().add(mrccn);
		getSystem().addConstraint(mrccn);

	}

	public void addRequireCompositeConstraint(FeatureGroup featureGroup1,
			String g1Type, FeatureGroup featureGroup2, String g2Type,
			Strength strength) {

		RequireCompositeConstraint rccn = new RequireCompositeConstraint(
				featureGroup1, g1Type, featureGroup2, g2Type, strength);
		getConstraints().add(rccn);
		getSystem().addConstraint(rccn);
	}

	public void addExcludeCompositeConstraint(FeatureGroup featureGroup1,
			String g1Type, FeatureGroup featureGroup2, String g2Type,
			Strength strength) {
		ExcludeCompositeConstraint eccn = new ExcludeCompositeConstraint(
				featureGroup1, g1Type, featureGroup2, g2Type, strength);
		getConstraints().add(eccn);
		getSystem().addConstraint(eccn);

	}

	public void addSetBindConstraint(Variable a, Strength strength) {
		SetBindConstraint sbcn = new SetBindConstraint(a, strength);
		getConstraints().add(sbcn);
		getSystem().addConstraint(sbcn);
	}

	public void addSetUnbindConstraint(Variable a, Strength strength) {
		SetUnbindConstraint subcn = new SetUnbindConstraint(a, strength);
		getConstraints().add(subcn);
		getSystem().addConstraint(subcn);
	}

	public void addSetUndecidedConstraint(Variable feature,
			Strength weakestStrength) {

		SetUndecidedConstraint sudcn = new SetUndecidedConstraint(feature,
				weakestStrength);
		getConstraints().add(sudcn);
		getSystem().addConstraint(sudcn);

	}

	// ***END OF ADD THE COSNTRIANTS***

	// ***START OF THE CONFIGURATION ACTIONS***
	public void bindFeature(Variable feature, Strength strength) {

		addSetBindConstraint(feature, strength);
	}

	public void unbindFeature(Variable feature, Strength strength) {

		addSetUnbindConstraint(feature, strength);
	}

	// ***END OF THE CONFIGURATION ACTIONS***

	// ***GETS AND SETS***
	public SkyBlue getSystem() {
		return system;
	}

	public void setSystem(SkyBlue system) {
		this.system = system;
	}

	public Vector<Variable> getFeatures() {
		return features;
	}

	public void setFeatures(Vector<Variable> features) {
		this.features = features;
	}

	public Vector<Constraint> getConstraints() {
		return constraints;
	}

	public void setConstraints(Vector<Constraint> constraints) {
		this.constraints = constraints;
	}
	public Vector<FeatureGroup> getFeatureGroups() {
		return featureGroups;
	}

	public void setFeatureGroups(Vector<FeatureGroup> featureGroups) {
		this.featureGroups = featureGroups;
	}
	// ***GETS AND SETS***



}
